<?php
/**
 * XuYuan Project
 *
 * @package	XueYuan 
 * @author	Jiuchi Team
 * @since	Version 1.0.0
 * @filesource
 */
defined('BASEPATH') OR exit('No direct script access allowed');
/**
 * Token Class for moblie terminal
 * @author Wayne Yu
 */
class Mytoken {
	private	$CI;
	private $redis_host			=	'localhost';
	private $redis_port			=	6379;
	private $token_expiration	=	86400;
	private $token_update_time	=	6000;
	private $time_reference		=	'time';
	private $now;
	
	/**
	 * Construnct function of this class
	 * @param array $params
	 */
	public function __construct($params = array()){
		//Set the super object
		$this->CI =& get_instance();
		
		//Load the lib required
		$this->CI->load->library('apioutput');
		$this->CI->load->helper('string');
		
		//Set preferences for our token system
		foreach (array('redis_host', 'redis_port', 'token_expiration',  'token_update_time', 'time_reference') as $key)
		{
			$this->$key = (isset($params[$key])) ? $params[$key] : $this->CI->config->item($key);
		}
		if ($this->token_expiration == 0)
		{
			$this->token_expiration = (60*60*24*7*2);//two weeks;
		}
		$this->now = $this->_get_time();
		
		//Initialize the Redis to store our token
		$this->redis = new Redis();
		$this->redis->connect($this->redis_host, $this->redis_port);
		$this->redis->setOption(Redis::OPT_PREFIX, 'mytoken:');// use custom prefix on all keys
		
		//Check the token
		$tokenCheck = $this->_init_token();
		switch ($tokenCheck){
			case 1://Token passed
				$this->updateToken();
				break;
			case 2://Token not passed
				$this->CI->apioutput->apiResponseFailauth();
				break;
			case 3://token is undefined
				break;
			case 4://Refuse the request
			default:
				$this->CI->apioutput->apiResponseRefuse();
		}
	}
	
	/**
	 * Create a token
	 * @return void
	 */
	public function createToken(){
		//Create new token
		$new_token = '';
		while (strlen($new_token) < 32)
		{
			$new_token .= mt_rand(0, mt_getrandmax());
		}
		$new_token .= $this->CI->input->ip_address();
		$new_token = md5(uniqid($new_token, TRUE));
		
		//Check the field `HTTP_AUTHORIZATION` in HTTP header
		$authorization = explode(',',$_SERVER['HTTP_AUTHORIZATION']);
		$this->userdata = array(
				'token'			=>	$new_token,
				'ip_address'	=>	$this->CI->input->ip_address(),
				'user_agent'	=>	substr($this->CI->input->user_agent(), 0, 120),
				'device_id'		=>	$authorization[2],
				'last_activity'	=>	$this->now
		);
		
		
		//Save the data to the redis
		$has_token = $this->redis->get($this->userdata['token']);
		if ($has_token) {
			$this->createToken();
			return FALSE;
		}
		$this->redis->setex($new_token, $this->token_expiration, json_encode($this->userdata));
	}
	
	/**
	 * Update token
	 */
	public function updateToken(){
		return;
		// We only update the token every 10 min
		if(!$this->token_update_time) $this->token_update_time = 600;
		if (($this->userdata['last_activity'] + $this->token_update_time) >= $this->now)
		{
			return;
		}
		
		//Save the old token to find the old data
		$old_token = $this->userdata['token'];
		
		//Create new token
		$new_token = '';
		while (strlen($new_token) < 32)
		{
			$new_token .= mt_rand(0, mt_getrandmax());
		}
		$new_token .= $this->CI->input->ip_address();
		$new_token = md5(uniqid($new_token, TRUE));
		
		//Check the new token is valid
		$has_token = $this->redis->get($new_token);
		if ($has_token) {
			$this->updateToken();
			return FALSE;
		}

		// Update the token data in the token data array and save the data to the redis
		$this->userdata['token'] = $new_token;
		$this->userdata['last_activity'] = $this->now;
		$this->redis->setex($new_token, $this->token_expiration, json_encode($this->userdata));
		$this->redis->delete($old_token);
	}
	
	/**
	 * Write new data to token
	 */
	public function writeToken(){
		// Run the update query
		$this->redis->multi()
			->delete($this->userdata['token'])
			->setex($this->userdata['token'], $this->token_expiration, json_encode($this->userdata))
			->exec();
	}
	
	/**
	 * 	Destroy token
	 */
	public function destroyToken(){
		if(isset($this->userdata['token']))
		$this->redis->delete($this->userdata['token']);
	}
	
	/**
	 * Initialization token
	 * Send 'undefind' when token & uid is not created
	 * 
	 * Code 1: Token passed
	 * Code 2: Token not passed
	 * Code 3: Token is undefined
	 * Code 4: Refuse the request
	 * @return Code
	 */
	private function _init_token(){
		//Check the authorization
		if(!isset($_SERVER['HTTP_AUTHORIZATION'])) return 4;
		$authorization = explode(',',$_SERVER['HTTP_AUTHORIZATION']);
		if(count($authorization) == 4){
			$uid		= $authorization[0];
			$sign		= $authorization[3];
			if($authorization[1] == 'undefined'){
				return 3;
			}
		} else {
			return 4;
		}
		
		//Token Info
		$tokenInfo = array();
		$tokenInfo['token']		= $authorization[1];
		$tokenInfo['ip_address'] 	= $this->CI->input->ip_address();
		$tokenInfo['device_id']		= $authorization[2];
		$tokenInfo['user_agent']	= trim(substr($this->CI->input->user_agent(), 0, 120));
		
		//Fetch the token from redis
		$row = json_decode($this->redis->get($tokenInfo['token']), TRUE);
		if(!$row){
			$this->destroyToken();
			return 2;
		}

		//Check the Ip\Useragent\Device
		if($row['ip_address']  != $tokenInfo['ip_address']){
			$this->destroyToken();
			return 2;
		}
		//if($row['user_agent'] != $tokenInfo['user_agent']){
			//$this->destroyToken();
			//return 2;
		//}
		if($row['device_id'] != $tokenInfo['device_id']){
			$this->destroyToken();
			return 2;
		}
		if($row['uid']  != $uid){
			$this->destroyToken();
			return 2;
		}
		$tokenInfo['last_activity'] = $row['last_activity'];
		
		//deal with the customer data
		if (isset($row['user_data']) AND $row['user_data'] != '')
		{
			$custom_data = $this->_unserialize($row['user_data']);
			if (is_array($custom_data))
			{
				foreach ($custom_data as $key => $val) {
					$tokenInfo[$key] = $val;
				}
			}
		}
		
		//set the data
		$this->userdata = $row;
		unset($tokenInfo);
		return 1;
	}
	
	/**
	 * Get the time, can either be GMT or server time
	 * @return	string
	 */
	private function _get_time()
	{
		if (strtolower($this->time_reference) == 'gmt')
		{
			$now = time();
			$time = mktime(gmdate("H", $now), gmdate("i", $now), gmdate("s", $now), gmdate("m", $now), gmdate("d", $now), gmdate("Y", $now));
		}
		else
		{
			$time = time();
		}
		return $time;
	}
		
	/**
	 * Serialize an array
	 *
	 * This function first converts any slashes found in the array to a temporary
	 * marker, so when it gets unserialized the slashes will be preserved
	 *
	 * @access	private
	 * @param	array
	 * @return	string
	 */
	private function _serialize($data)
	{
		if (is_array($data))
		{
			foreach ($data as $key => $val)
			{
				if (is_string($val))
				{
					$data[$key] = str_replace('\\', '{{slash}}', $val);
				}
			}
		}
		else
		{
			if (is_string($data))
			{
				$data = str_replace('\\', '{{slash}}', $data);
			}
		}

		return serialize($data);
	}

	/**
	 * Unserialize
	 *
	 * This function unserializes a data string, then converts any
	 * temporary slash markers back to actual slashes
	 *
	 * @access	private
	 * @param	array
	 * @return	string
	 */
	private function _unserialize($data)
	{
		$data = @unserialize(strip_slashes($data));

		if (is_array($data))
		{
			foreach ($data as $key => $val)
			{
				if (is_string($val))
				{
					$data[$key] = str_replace('{{slash}}', '\\', $val);
				}
			}

			return $data;
		}

		return (is_string($data)) ? str_replace('{{slash}}', '\\', $data) : $data;
	}
	
	/**
	 * Add or change data in the "userdata" array
	 *
	 * @access	public
	 * @param	mixed
	 * @param	string
	 * @return	void
	 */
	function set_userdata($newdata = array(), $newval = '')
	{
		if (is_string($newdata))
		{
			$newdata = array($newdata => $newval);
		}
	
		if (count($newdata) > 0)
		{
			foreach ($newdata as $key => $val)
			{
				$this->userdata[$key] = $val;
			}
		}
	
		$this->writeToken();
	}
	
	/**
	 * Fetch a specific item from the session array
	 *
	 * @access	public
	 * @param	string
	 * @return	string
	 */
	function userdata($item)
	{
		return ( ! isset($this->userdata[$item])) ? FALSE : $this->userdata[$item];
	}
	
	// --------------------------------------------------------------------
	
	/**
	 * Fetch all session data
	 *
	 * @access	public
	 * @return	array
	 */
	function all_userdata()
	{
		return $this->userdata;
	}
}